"""
pvis/pvis_main.py
Defines some package-wide functions including the Run function and creates
the default scene instance.

Written By:
    James Thomas
    Email: jim@houseoftechnology.org
    Web: http://mission-cognition.houseoftechnology.org/

Copyright 2009-2012

This file is part of the PandaODElib distribution.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version. The text of the
GNU Lesser General Public License is included with this library in
the file named LICENSE.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""
# TODO: Create a visual-like display class to wrap most of this code
import __builtin__
import math

from pandac.PandaModules import ClockObject, Vec4, Vec3, Quat, Mat4
from pandac.PandaModules import AmbientLight,Spotlight

_TargetFPS = 30

def get_selected():
    return render

def rate(TargetFPS):
    global _TargetFPS
    _TargetFPS = TargetFPS

def QuatInverse(Q):
    """Panda3d inverse does not conform to any definition I've seen.  Use this instead."""
    return Q.conjugate() / Q.lengthSquared()

xAxis = Quat(1,0,1,0)
xAxis.normalize()
xAxisInv = QuatInverse(xAxis)

yAxis = Quat(1,-1,0,0)
yAxis.normalize()
yAxisInv = QuatInverse(yAxis)

zAxis = Quat()
zAxisInv = QuatInverse(zAxis)

def QuatMultiply(Q1, Q2):
    """Panda3d multiplication order seems backwards from all the definitions I've seen. Use this instead"""
    return Q2 * Q1

def QuatFromVectorPair(v1, v2):
    """Creates a quaternion which will rotate v1 onto v2"""
    nv1 = Vec3(v1)
    nv1.normalize()
    nv2 = Vec3(v2)
    nv2.normalize()

    # Compute the cosine of the angle between the two vectors
    cosAngle = nv1.dot(nv2)

    if cosAngle > (1.0 - 1e-12):
        # The two vectors are essentially the same, return a unit quaternion
        return Quat()

    # Get axis of rotation from the cross product, find a useful
    # axis if the vectors nearly lie on a line
    if abs(nv1.dot(nv2)) > 0.98:
        if abs(nv1.dot(Vec3(1,0,0))) > 0.8:
            # Direction is close to x, cross with y
            vAxis = nv1.cross(Vec3(0,1,0))
            vAxis.normalize()
        elif abs(nv1.dot(Vec3(0,1,0))) > 0.8:
            # Direction is close to y, cross with z
            vAxis = nv1.cross(Vec3(0,0,1))
            vAxis.normalize()
        else:
            # It is either close to z or not close to any axis so just cross with x
            vAxis = nv1.cross(Vec3(1,0,0))
            vAxis.normalize()
            
    else:
        # Safe to use the cross product for the axis 
        vAxis = nv1.cross(nv2)
        vAxis.normalize()

    q = Quat()
    q.setFromAxisAngleRad(math.acos(cosAngle), vAxis)
    return q

def QuatFromQuaternionPair(q1, q2): 
    """Creates a quaternion q which will rotate q1 to q2 such that RotateQuat(q, q1) -> q2"""
    return QuatMultiply(q2, QuatInverse(q1))

def QuatFromForwardUp(forward, up):
    """Creates a quaternion with the specified forward and up vectors"""
    q = QuatFromVectorPair((0,0,1), forward);
    return RotateQuat(QuatFromVectorPair((0,1,0), up), q)

def RotateQuat(q1, q2):
    """Rotate q2 by q1"""
    return QuatMultiply(q1, q2)

def RotatePoint(vPoint, qRot):
    """Rotates vPoint by this quaternion"""
    return qRot.xform(vPoint)

# This is my attempt to fix confusion that Panda3d adds by
# stuffing a bunch of gloabls into __builtin__
# Users of this package can just get them from here
# and nobody will be confused, least of all PyLint
# http://www.panda3d.org/manual/index.php/ShowBase
if not hasattr(__builtin__, 'base'):
    import sys
    if 'wx.__version__' in sys.modules:
        print 'Doing window-less startup in anticipation of wx windows based app'
        from pandac.PandaModules import loadPrcFileData
        loadPrcFileData('startup', 'window-type none')
        loadPrcFileData('', 'threading-model Cull/Draw')
    
    from direct.showbase import ShowBase
    ShowBase.ShowBase()
       
base = __builtin__.base #@UndefinedVariable
render2d = __builtin__.render2d #@UndefinedVariable
aspect2d = __builtin__.aspect2d #@UndefinedVariable
pixel2d = __builtin__.pixel2d #@UndefinedVariable
render = __builtin__.render #@UndefinedVariable
hidden = __builtin__.hidden #@UndefinedVariable
camera = __builtin__.camera #@UndefinedVariable
loader = __builtin__.loader #@UndefinedVariable
taskMgr = __builtin__.taskMgr #@UndefinedVariable
jobMgr = __builtin__.jobMgr #@UndefinedVariable
eventMgr = __builtin__.eventMgr #@UndefinedVariable
messenger = __builtin__.messenger #@UndefinedVariable
bboard = __builtin__.bboard #@UndefinedVariable
run = __builtin__.run #@UndefinedVariable
ostream = __builtin__.ostream #@UndefinedVariable
directNotify = __builtin__.directNotify #@UndefinedVariable
giveNotify = __builtin__.giveNotify #@UndefinedVariable
globalClock = __builtin__.globalClock #@UndefinedVariable
vfs = __builtin__.vfs #@UndefinedVariable
cpMgr = __builtin__.cpMgr #@UndefinedVariable
cvMgr = __builtin__.cvMgr #@UndefinedVariable
pandaSystem = __builtin__.pandaSystem #@UndefinedVariable
wantUberdog = __builtin__.wantUberdog #@UndefinedVariable

def Run(UpdateLoopFunc=lambda task:task.cont):
    globalClock = ClockObject.getGlobalClock()
    globalClock.setMode(ClockObject.MLimited)
    globalClock.setFrameRate(_TargetFPS)
    taskMgr.add(UpdateLoopFunc, 'Update Loop')

    slight1 = render.attachNewNode(Spotlight("Spot1")) 
    direction = Vec3(0.22, 0.44, 0.88)
    slight1.setPos(-direction*1e6) 
    slight1.lookAt(0,0,0) 
    slight1.setColor(.8,.8,.8,1)
    render.setLight(slight1) 
    slight1.node().getLens().setFov(180) 
    
    slight2 = render.attachNewNode(Spotlight("Spot2")) 
    direction = Vec3(-0.88, -0.22, -0.44)
    slight2.setPos(-direction*1e6) 
    slight2.lookAt(0,0,0)
    slight2.setColor(.3,.3,.3,1)
    render.setLight(slight2) 
    slight2.node().getLens().setFov(180) 
    
    alight = render.attachNewNode(AmbientLight("Ambient")) 
    alight.node().setColor(Vec4(0.2, 0.2, 0.2, 1)) 
    render.setLight(alight) 
    render.setShaderAuto()
    
    base.setFrameRateMeter(True)
    
    if base.camera.getPos().almostEqual((0,0,0)):
        # Create an initial camera position
        base.disableMouse()
        base.camera.setPos(4, 4, 1)
        base.camera.lookAt(0, 0, 0)
    
        # Turn mouse control back on
        mat=Mat4(base.camera.getMat())
        mat.invertInPlace()
        base.mouseInterfaceNode.setMat(mat)
        base.enableMouse()

    run()
    
if __name__ == '__main__':
    """Run a test"""
    #from direct.directbase import DirectStart
    from shapes import *
    from color import *
    from materials import *
    

    qx = Quat(xAxis)
    qy = Quat(yAxis)
    qz = Quat(zAxis)
    qxy = QuatFromQuaternionPair(qx,qy)
    print qxy
    qr =  RotateQuat(qxy, qx)
    print qr, '==', qy
    assert qr.almostEqual(qy)
    qyz = QuatFromQuaternionPair(qy,qz)
    assert RotateQuat(qyz, qy).almostEqual(qz)
    qzx = QuatFromQuaternionPair(qz,qx)
    assert RotateQuat(qzx, qz).almostEqual(qx)

    v1 = Vec3(0,0,1)
    v2 = Vec3(0,1,0)
    qr = QuatFromVectorPair(v1, v2)
    print v2, RotatePoint(v1, qr)
    assert v2.almostEqual(RotatePoint(v1, qr))
    
    # Create an axis reference, x = red, y = green, z = blue
    a1 = arrow(pos=(-.5,0,0), q=xAxis, shaftwidth=0.1, color=(1,0,0))
    a2 = arrow(pos=(0,-.5,0), q=yAxis, shaftwidth=0.1, color=(0,1,0))
    a3 = arrow(pos=(0,0,-.5), q=zAxis, shaftwidth=0.1, color=(0,0,1))
    s = sphere(radius = 0.1)

    #scene = disp.display()
    #lbl1 = label(pos=(scene.width/2, scene.height/2,0), text='Hello World', font='Mono')
    #lbl2 = label(pos=(scene.width/2, scene.height/2 -20,0), text='Hello World', font='Sans')
    #lbl3 = label(pos=(scene.width/2, scene.height/2 -40,0), text='Hello World', font='Serif')
    #txt = text(pos=(-1.5,0,0), height=0.5, text='Hello World')

    fr = frame()
    #b1 = box(size=(1,.2,.2), pos=(-.5,0,0), q=xAxis, color=red)
    #b2 = box(size=(1,.2,.2), pos=(0,-.5,0), q=yAxis, color=green)
    #b3 = box(size=(1,.2,.2), pos=(0,0,-.5), q=zAxis, color=blue)
    c1 = cylinder(pos=(-.5,0,0), q=xAxis, length=.25, radius=.25, material=wood)
    c2 = cylinder(pos=(0,-.5,0), q=yAxis, length=.25, radius=.25, material=bricks)
    c3 = cylinder(pos=(0,0,-.5), q=zAxis, length=.25, radius=.25, material=turbulence)
    #earth = sphere(radius=.25, material=earth)
    #cn = cone(frame=fr)
    #p = pyramid(height=4, width=2)
    #cr = curve(pos=[(0,0,0), (1,0,0), (1,1,0), (1,1,1)])
    #t1 = ring(pos=(1,0,0))
    #t2 = ring(pos=(1,0,0), q=yAxis)
    #t3 = ring(pos=(1,0,0), q=zAxis)
    #e = ellipsoid(size=(1,2,3))
    #f = faces(pos=((0,0,0), (1,0,0), (1,1,0), (1,1,1), (1,0,0), (0,0,0)))
    #f.make_normals()

    #def loop():
    #    import random
    #    fr.q *= Quaternion(1,random.gauss(0,0.003),random.gauss(0,0.004),random.gauss(0,0.005)).normalize()
    #    return task.cont
    #Run(loop)
    Run()